package stream.basic_data;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.zip.ZipOutputStream;

import static java.nio.charset.Charset.*;

/**
 * @CreateBy zhangdingyi
 * @Date 2022-10-16
 * @Content 输入输出流的学习
 * <p>
 * 输入输出流，就会涉及到一个东西，就是字符编码问题
 * UTF-8 （8-bit Unicode Transformation Format） 用来表示Unicode标准中的任何字符，且其编码中的第一个字节仍与ASCII兼容，
 * 这使得原来处理ASCII字符的软件无须或只须做少部份修改，即可继续使用。
 * <p>
 * ASCII（American Standard Code for Information Interchange，美国信息交换标准代码）
 * 是基于拉丁字母的一套电脑编码系统，主要用于显示现代英语和其他西欧语言。
 * 它是最通用的信息交换标准，并等同于国际标准ISO/IEC 646
 * <p>
 * 统一码（Unicode），也叫万国码、单一码，是计算机科学领域里的一项业界标准，包括字符集、编码方案等。
 * Unicode是为了解决传统的字符编码方案的局限而产生的，
 * 它为每种语言中的每个字符设定了 统一 并且 唯一 的 二进制编码 ，
 * 以满足跨语言、跨平台进行文本转换、处理的要求。
 * <p>
 * java在写入数据的时候也可以对字符编码进行修改
 * Charset
 * <p>
 * 文本格式数据 是人类可读取的，但在传数据的时候远远没有二进制数据那么高效
 */
public class IOSteam {

    static String path = "emp.txt";
    static String csn = "UTF-8";
    static Employee[] staff = new Employee[3];

    static {
        staff[0] = new Employee("Carl Cracker", 75000, 1896, 12, 15);
        staff[1] = new Employee("Harry Cracker", 5000, 1940, 10, 12);
        staff[2] = new Employee("Dy Cracker", 6000, 1998, 11, 11);
    }


    public static void main(String[] args) throws IOException {
        //导出数据
        //printWriterData();
        //导入数据
        //allReadData();

        //根据模板 导入导出
        //saveFormatOne();

        //二进制数据 导入导出 -有一些bug 未处理
        //binaryDataIO();

        //zip 压缩文件
        //zipInData();

        //对象流
        //objectStream();

        //克隆对象 --有bug
        //cloneData();

        System.out.println("------------");
        //System.getProperty("user.dir");
    }


    /**
     * 克隆对象 --这个代码也有问题， 后面再 进行学习
     */
    private static void cloneData() {
        Employee employee = new Employee("Carl Cracker", 75000, 1896, 12, 15);
        //Employee employee2 = (Employee) employee.clone();
        employee.raiseSalary(10);
    }

    /**
     * 如果需要克隆对象，这个克隆方法不能随便使用，需要先做一个操作
     */
    public class SerialCloneable implements Cloneable, Serializable {
        public Object clone() throws CloneNotSupportedException {
            try {
                ByteArrayOutputStream bout = new ByteArrayOutputStream();
                try (ObjectOutputStream out = new ObjectOutputStream(bout)) {
                    out.writeObject(this);
                }
                try (InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
                    ObjectInputStream in = new ObjectInputStream(bin);
                    return in.readObject();
                }
            } catch (IOException | ClassNotFoundException e) {
                CloneNotSupportedException e2 = new CloneNotSupportedException();
                e2.initCause(e);
                throw new RuntimeException(e2);
            }
        }
    }


    /**
     * 对象流 -与序列化
     * -序列化自百度
     * 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
     * 在序列化期间，对象将其当前状态写入到临时或持久性存储区。
     * 以后，可以通过从存储区中读取或反序列化对象的状态，重新创建该对象。
     * <p>
     * 序列化的理解 -简单的说，序列化就是把一个对象的不同部分拆分开来，
     * 进行标记并存储至内存中（这个叫序列化）
     * <p>
     * 那么当 用户想要获取序号化对象时就需要对序列化对象进行反序列化 然后就 解析至代码运行环境中
     * <p>
     * 这个过程就像是 一个加密解密的过程
     * 把一个对象加密变成二进制数据存储至硬盘中 这个二进制文件称为 二进制对象-序列化
     * 把这个 二进制对象 从硬盘中读取至代码运行环境中需要解密 -然后这个过程就是 反序列化
     * <p>
     * 然后就是手动的修改序列化的字段 （可以在对应的序列化数据的前面添加上一个 transient 关键字 ，那么该字段就不能进行序列化）
     */
    private static void objectStream() {
    }

    /**
     * 创建 输入输出流 并做一个导出导入操作 -后面自行百度学习，这本书对该功能讲解的较少
     */
    private static void zipInData() throws FileNotFoundException {
        FileOutputStream os = new FileOutputStream("emp.zip");
        ZipOutputStream zipOutputStream = new ZipOutputStream(os);

    }

    /**
     * 文本格式数据 是人类可读取的，但在传数据的时候远远没有二进制数据那么高效
     * DataInputStream类实现了DataInput接口，为了从文件中读入二进制数据，可以将DataInputStream与某个字节源相组合
     * DataOutputStream类实现了DataOutput接口,写出文件二进制数据
     * <p>
     * 随机访问文件
     */
    private static void binaryDataIO() throws IOException {
        try (DataOutputStream out = new DataOutputStream(new FileOutputStream(path))) {
            for (Employee employee : staff) {
                writeData(out, employee);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //“r”（用于读入访问）或“rw”（用于读入/写出访问）
        try (RandomAccessFile in = new RandomAccessFile(path, "r")) {
            int n = (int) (in.length() / Employee.RECORD_SIZE);
            Employee[] newStaff = new Employee[n];

            for (int i = n - 1; i >= 0; i++) {
                newStaff[i] = new Employee();
                in.seek(i * Employee.RECORD_SIZE);
                newStaff[i] = readData(in);
            }

            for (Employee employee : newStaff) {
                System.out.println(employee);
            }

        }

    }

    public static Employee readData(DataInput in) throws IOException {
        String name = readFixedString(Employee.NAME_SIZE, in);
        double salary = in.readDouble();
        int y = in.readInt();
        int m = in.readInt();
        int d = in.readInt();
        return new Employee(name, salary, y, m - 1, d);
    }

    /**
     * 二进制流的写入方法
     *
     * @param out
     * @param employee
     */
    private static void writeData(DataOutput out, Employee employee) throws IOException {
        writeFixedString(employee.getName(), employee.getName().length(), out);
        out.writeDouble(employee.getSalary());

        LocalDate hireDate = LocalDate.parse(employee.getHireDay());

        out.writeInt(hireDate.getYear());
        out.writeInt(hireDate.getMonthValue());
        out.writeInt(hireDate.getDayOfMonth());

    }

    /**
     * 以二进制的方式 写出所有的字段
     *
     * @param s
     * @param size
     * @param out
     * @throws IOException
     */
    private static void writeFixedString(String s, int size, DataOutput out) throws IOException {
        for (int i = 0; i < size; i++) {
            char ch = 0;
            if (i < s.length()) {
                ch = s.charAt(i);
                //把字符 类型转换成二进制输出
                out.writeChar(ch);
            }
        }

    }

    /**
     * 读取二进制数据
     *
     * @param size
     * @param in
     * @return
     * @throws IOException
     */
    private static String readFixedString(int size, DataInput in) throws IOException {
        StringBuilder sb = new StringBuilder(size);
        int i = 0;
        boolean more = true;
        while (more && i < size) {
            char c = in.readChar();
            i++;
            if (c == 0) {
                more = false;
            } else {
                sb.append(c);
            }
        }

        in.skipBytes(2 * (size - 1));

        return sb.toString();
    }


    /**
     * 以文本格式存储 数据
     */
    static void saveFormatOne() {
        //写入
        try (PrintWriter out = new PrintWriter(path, csn)) {
            writeData(staff, out);

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        //读取 Scanner 还可以这样用！！
        try (Scanner scanner = new Scanner(new FileInputStream(path), csn)) {
            Employee[] newStaff = readData(scanner);
            for (Employee employee : newStaff) {
                System.out.println(employee.getHireDay());
            }


        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }

    }

    static void writeData(Employee[] employees, PrintWriter out) {
        out.println(employees.length);
        for (Employee employee : employees) {
            writeEmployee(out, employee);
        }

    }

    static Employee[] readData(Scanner in) {
        int n = in.nextInt();
        in.nextLine();

        Employee[] staff = new Employee[n];
        for (int i = 0; i < n; i++) {
            staff[i] = readEmployee(in);
        }
        return staff;
    }

    /**
     * 这个就是所谓的格式写入 --其实就是写入的时候，添加自己想要的格式（字符）
     *
     * @param out
     * @param employee
     */
    static void writeEmployee(PrintWriter out, Employee employee) {
        out.println(employee.getName() + "|" + employee.getSalary() + "|" + employee.getHireDay());
    }

    /**
     * 读取数据
     *
     * @param in
     * @return
     */
    public static Employee readEmployee(Scanner in) {
        String line = in.nextLine();
        String[] tokens = line.split("\\|");
        String name = tokens[0];
        double salary = Double.parseDouble(tokens[1]);
        System.out.println("salary = " + salary);

        //字符格式正确的前提下，可以对日期进行转换
        System.out.println("tokens[2] = " + tokens[2]);
        LocalDate hireDate = LocalDate.parse(tokens[2]);
        System.out.println("hireDate = " + hireDate);
        int year = hireDate.getYear();
        int month = hireDate.getMonthValue();
        int day = hireDate.getDayOfMonth();
        return new Employee(name, salary, year, month, day);

    }

    /**
     * 需要类继承该接口 Externalizable
     * 以下两个方法就是可以对应的修改 -序列化的默认配置（类还可以定义它自己的机制）
     * -想咋样设置就怎么设置（涉及太少，不太清楚）
     *
     * @param out the stream to write the object to
     * @throws IOException
     */
/*    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    }*/

    static class Employee {
        private String name;
        private double salary;
        private int year;
        private int month;
        private int day;

        public static int RECORD_SIZE = 1;
        public static int NAME_SIZE = 2;

        public Employee() {
        }

        public Employee(String name, double salary, int year, int month, int day) {
            this.name = name;
            this.salary = salary;
            this.year = year;
            this.month = month;
            this.day = day;
        }

        public String getHireDay() {
            return year + "-" + month + "-" + day;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public double getSalary() {
            return salary;
        }

        public void setSalary(double salary) {
            this.salary = salary;
        }

        public int getYear() {
            return year;
        }

        public void setYear(int year) {
            this.year = year;
        }

        public int getMonth() {
            return month;
        }

        public void setMonth(int month) {
            this.month = month;
        }

        public int getDay() {
            return day;
        }

        public void setDay(int day) {
            this.day = day;
        }

        public void raiseSalary(double byPercent) {
            double raise = salary * byPercent / 100;
            salary += raise;
        }

        @Override
        public String toString() {
            return "Employee{" +
                    "name='" + name + '\'' +
                    ", salary=" + salary +
                    ", year=" + year +
                    ", month=" + month +
                    ", day=" + day +
                    '}';
        }
    }

    /**
     * 这个与 输出流不同，输入流直接调用方法就能够使用，不用从管道中取出数据
     *
     * @throws IOException
     */
    static void allReadData() throws IOException {
        String readData = new String(Files.readAllBytes(Paths.get(path)));
        System.out.println("readData = " + readData);
        List<String> strings = Files.readAllLines(Paths.get(path));
        System.out.println("strings = " + strings);
        System.out.println("strings = " + strings.get(0));

    }

    /**
     * @throws FileNotFoundException
     * @throws UnsupportedEncodingException
     * @CreateBy zhangdingyi
     * 这个东西，就相当于输出流一样，记得需要close 把管道中的数据取出并且关闭流
     * 简单的说这个 PrintWriter 就是一个输出流
     * 仅需要 输入 文件路径与 格式即可打印出对应的数据
     */
    static void printWriterData() throws FileNotFoundException, UnsupportedEncodingException {
        PrintWriter out = new PrintWriter(path, csn);
        System.out.println("out = " + out);
        out.print("测试打印");
        //这个可以检查是否有错误出现
        //如果产生格式化或输出错误，则返回true。一旦这个流碰到了错误，它就受到了污染，并且所有对checkError的调用都将返回true。
        boolean b = out.checkError();
        out.close();
        //上面那个等同于下面这个
        //PrintWriter outF = new PrintWriter(new FileOutputStream(path),true);
        //outF.print("adsadasda");
        //outF.close();

    }

    /**
     * 输入输出流 字符编码方式
     * 简单的说这个就是可以读取任意类型的数据，再进行读取出来，这里用的是最原始的编码格式因此可以适应大部分数据
     * 但还有更基础的编码格式，就是二进制格式，这种格式可以适应不同软件之间的交互
     * <p>
     * 这个输入流读入器会假定使用主机系统所使用的默认字符编码方式。
     * 在桌面操作系统中，它可能是像Windows 1252或MacRoman这样的古老的字符编码方式。
     * 你应该总是在InputStreamReader的构造器中选择一种具体的编码方式
     * <p>
     * OutputStreamWriter类将使用选定的字符编码方式，把Unicode码元的输出流转换为字节流。
     * 而InputStreamReader类将包含字节（用某种字符编码方式表示的字符）的输入流转换为可以产生Unicode码元的读入器。
     */
    void readStreamReader() throws IOException {
        Reader in = new InputStreamReader(System.in);
        char[] data = new char[1024];
        in.read(data);
        System.out.println(data[0]);
    }
}
